﻿using DART.Detector.IRay.Wrapper;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DART.Detector.IRay
{
    /// <summary>
    /// 探测器连接的网卡IP:192.168.8.188
    /// </summary>
    public class IRayDetector  :IDisposable
    {
        private readonly DetectorCounter counter = new DetectorCounter();
        private readonly DetectorStatus status = new DetectorStatus();

        /// <summary>
        /// 探测器连接的网卡IP:192.168.8.188
        /// </summary>
        private IRayDetector()
        {
            Invoke.Event += InvokeEvent;
        }

        /// <summary>
        /// 单例
        /// </summary>
        public static readonly IRayDetector Instance = new IRayDetector();

        /// <summary>
        /// 异步调度器，默认顺序调度。
        /// </summary>
        public TaskFactory TaskFactory { get => Invoke.TaskFactory; set => Invoke.TaskFactory = value; }

        /// <summary>
        /// 获取底层封装
        /// </summary>
        public IRayInvoke Invoke => IRayInvoke.Instance;

        /// <summary>
        /// 探测器设备底层事件
        /// </summary>
        public event EventHandler PreviewDetectorEvent;

        /// <summary>
        /// 探测器事件
        /// </summary>
        public event EventHandler<DetectorEventArgs> DetectorEvent;

        public IDetectorParam Param { get; } = new IRayExtensions.DetectorParam();

        public IDetectorCounter Counter => counter;

        protected virtual void InvokeEvent(object sender, IRayEventArgs e)
        {
            PreviewDetectorEvent?.Invoke(this, e);
            var ex = e.Param2 != IRayError.OK ? new IRayException(e) : null;
            if (e.EventID == IRayEvent.Image) // 采集图像
            {
                var image = e.Image;
                counter.SequenceIndex = counter.SequenceCount;
                counter.ExposureCount++;
                counter.SequenceExposureCount++;
                if (!image.IsPreExposure)
                {
                    counter.ImageCount++;
                    counter.SequenceImageCount++;
                }
                if (counter.SequenceExposureCount == 1) // 仅在每个序列的第1次曝光时获取探测器模式信息
                {
                    var acq = GetAcqAsync().Result;
                    status.AutoExposure = acq.AutoExposure;
                    status.Mode = acq.Mode;
                    status.Filter = acq.Filter;
                }
                status.Temperature = image.Temperature;
                var statusClone = status.Clone();
                var counterClone = counter.Clone();
                image.Tags[nameof(IDetectorCounter)] = counterClone;
                image.Tags[nameof(IDetectorStatus)] = statusClone;
                DetectorEvent?.Invoke(this, new DetectorEventArgs(statusClone, counterClone, image, ex, e));
            }
            else if (e.EventID == IRayEvent.MammoFlowEnd) // 序列结束
            {
                counter.SequenceCount++;
                DetectorEvent?.Invoke(this, new DetectorEventArgs(status.Clone(), counter.Clone(), null, ex, e));
                counter.SequenceExposureCount = 0;
                counter.SequenceImageCount = 0;
            }
            else // 其它事件
            {
                DetectorEvent?.Invoke(this, new DetectorEventArgs(null, null, null, ex, e));
            }
        }

        /// <summary>
        /// 初始化探测器
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task InitAsync(CancellationToken cancellation = default)
        {
            var workDirectory = Path.Combine(AppContext.BaseDirectory, Param.GetWorkDirectory());
            if (await Invoke.CreateAsync(workDirectory).ConfigureAwait(false))
            { 
                await Invoke.ConnectAsync(10, cancellation).ConfigureAwait(false);
                await Invoke.SetAttrsAsync(Param, cancellation).ConfigureAwait(false); 
            }
        }
 
        /// <summary>
        /// 获取设备信息，需要先初始化
        /// </summary>
        /// <param name="keys"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<IReadOnlyDictionary<string, string>> GetInfoAsync(IEnumerable<string> keys = null, CancellationToken cancellationToken = default)
        {
            await InitAsync(cancellationToken).ConfigureAwait(false);
            return await Invoke.TaskFactory.StartNew(() =>
            {
                var output = new Dictionary<string, string>();
                if (keys == null || !keys.Any())
                {
                    foreach (var item in Invoke.GetAllAvailableAttrs())
                        output[$"{item}"] = Invoke.GetAttr(item).To<string>();
                }
                else
                {
                    foreach (var item in keys)
                    {
                        if (Enum.TryParse<IRayAttr>(item, true, out var a))
                            output[$"{item}"] = Invoke.GetAttr(a).To<string>();
                    }
                }
                return output;
            }, cancellationToken).ConfigureAwait(false);
        }

        private Dictionary<string, Dictionary<string, string>> detectorModesCache = new Dictionary<string, Dictionary<string, string>>(); // 配置文件缓存

        /// <summary>
        /// 设置探测器参数
        /// </summary>
        /// <param name="param"></param>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task SetParamAsync(IDictionary<string, object> param, CancellationToken cancellation = default)
        { 
            if(!detectorModesCache.Any())
                detectorModesCache = GetConfig("MammoDetectorMode.ini");
            // 只有非3D模式并且参数中有设置曝光窗口 才需要设置多帧叠加采集。
            if (status.Mode != DetectorMode.Tomo && param.TryGetValue<int>("Time", out var expWin))
            {
                // IRAY反馈PGA参数实际未使用，为了防止sdk出现未定义行为，尽可能使用从配置文件获取的实际pga值，未取到则使用0
                var pga = detectorModesCache.TryGetValue("DetectorMode2", out var _detectorMode)
                    && _detectorMode.TryGetValue("PGA", out var _pgaString) 
                    && int.TryParse(_pgaString, out var _pga)
                    ? _pga : 0; 
                await Invoke.SetDetectorParamAsync(new IRayDetectorParam
                {
                    ExpWin = expWin,
                    AcqType = PrepCapMode.Acq2,
                    PGA = pga,
                }, cancellation).ConfigureAwait(false);

                var frame = param.TryGetValue<int>("Frame", out var _frame)
                    ? _frame
                    : (Param.TryGetValue<int>(nameof(IRayAttr.UROMFrameNumber), out var _frame2) ? _frame2 : 2);
                var prevFrame = Invoke.GetAttr(IRayAttr.UROMFrameNumber).To<int>();

                if (prevFrame != frame) // 需要修改多帧叠加帧数
                {
                    await Invoke.SetAttrsAsync(new Dictionary<string, object>
                    {
                        [nameof(IRayAttr.UROMFrameNumberW)] = frame
                    }, cancellation).ConfigureAwait(false);
                }
                //expMode.TriggerMode = TriggerMode.Prep; // 多帧叠加采集修改为Prep模式，Outer也支持多帧叠加
            }
        }

        /// <summary>
        /// 设置探测器采集模式
        /// </summary>
        /// <param name="param">["<see cref="TriggerMode"/>"]</param>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task SetAcqAsync(AcqParam param, CancellationToken cancellation = default)
        {
            await Invoke.WaitReadyAsync(cancellation).ConfigureAwait(false);
            var mode = param.Mode.Swap();
            var expMode = IRayModeParam.Create(mode, param.AutoExposure, (Filter)param.Filter);
            if(param.TryGetValue<TriggerMode>(nameof(TriggerMode),out var triggerMode))
                expMode.TriggerMode = triggerMode;
            await Invoke.SetExposureAsync(expMode, cancellation).ConfigureAwait(false);
        }

        /// <summary>
        /// 获取探测器当前采集模式
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task<AcqParam> GetAcqAsync(CancellationToken cancellation = default)
        {
            var prop = await Invoke.GetExposureAsync().ConfigureAwait(false);
            return new AcqParam { AutoExposure = prop.AutoExposure, Mode = prop.AppMode.Swap(), Filter = (int)prop.Filter };
        }

        private Dictionary<string, Dictionary<string, string>> GetConfig(string file)
        {
            var ini = Path.Combine(Invoke.WorkDirectory, file);
            var config = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
            if (File.Exists(ini))
            {
                var key = string.Empty;
                /// 读取ini文件中的有效行
                foreach (var line in File.ReadLines(ini, Encoding.UTF8)
                    .Select(_ => _.Trim())
                    .Where(_ => !string.IsNullOrWhiteSpace(_) && !_.StartsWith(";")))
                {
                    if (line.StartsWith("["))
                    {
                        key = line;
                        config.Add(key, new Dictionary<string, string>());
                        continue;
                    }
                    if (!config.Any()) continue;
                    var splitIndex = line.IndexOf('=');
                    if (splitIndex > 0)
                        config[key][line.Substring(0, splitIndex).Trim()] = line.Substring(splitIndex + 1).Trim();
                }
            }
            return config;
        }
 
        #region IDisposable

        private bool disposedValue;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                Invoke.Event -= InvokeEvent;
                Invoke.Dispose();
                disposedValue = true;
            }
        }

        ~IRayDetector()
        {
            Dispose(disposing: false);
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        #endregion IDisposable
    }
}